#region Imported Namespaces


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;

using WtfManager.WindowsNative.User32;


#endregion


namespace WtfManager.WindowsNative.Shell32
{
  [ComVisible(false)]
  public sealed class Pidl: IDisposable, IComparable, ICloneable
  {
    #region Fields


    private const int S_OK = 0;
    private static readonly IShellFolder c_desktopFolder;
    private static readonly IntPtr c_desktopPidl;
    private static Guid IID_IShellFolder = new Guid("{000214E6-0000-0000-C000-000000000046}");
    private FileInfo _flags;
    private Icon _icon;
    private int _iconOverlayIndex;
    private SFGAO m_attributes;
    private string m_displayName;
    private string m_physicalPath;
    private IntPtr m_pidl;
    private string m_typeName;


    #endregion


    #region Constructors/Finalizers


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    static Pidl()
    {
      // Get a reference to the desktop folder.
      Shell32Methods.SHGetDesktopFolder(out c_desktopFolder);

      var hToken = new IntPtr(-1);

      // Get the pidl for the desktop folder.
      Shell32Methods.SHGetFolderLocation(IntPtr.Zero, CSIDL.CSIDL_DESKTOP, hToken, 0, out c_desktopPidl);
    }


    public Pidl()
    {
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public Pidl(IntPtr pidl, FileInfo flags)
    {
      Open(pidl, flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public Pidl(IntPtr parentPidl, IntPtr pidl, FileInfo flags)
    {
      Open(parentPidl, pidl, flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public Pidl(Environment.SpecialFolder specialFolder, FileInfo flags)
    {
      Open(specialFolder, flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public Pidl(string fullPath, FileInfo flags)
    {
      Open(fullPath, flags);
    }


    #endregion


    #region Properties/Indexers


    public bool CanCopy
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_CANCOPY) != 0;
      }
    }

    public bool CanDelete
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_CANDELETE) != 0;
      }
    }

    public bool CanMove
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_CANMOVE) != 0;
      }
    }

    public bool CanRename
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_CANRENAME) != 0;
      }
    }

    public string DisplayName
    {
      get
      {
        return m_displayName;
      }
    }

    public FileInfo Flags
    {
      get
      {
        return _flags;
      }
    }

    public bool HasSubfolders
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_HASSUBFOLDER) != 0;
      }
    }

    public Icon Icon
    {
      get
      {
        return _icon;
      }
    }

    public int IconOverlayIndex
    {
      get
      {
        return _iconOverlayIndex;
      }
    }

    public bool IsBrowsable
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_BROWSABLE) != 0;
      }
    }

    public bool IsCompressed
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_COMPRESSED) != 0;
      }
    }

    public bool IsDesktop
    {
      get
      {
        // Ask the desktop to determine if they are equal.
        return c_desktopFolder.CompareIDs(SHCIDS.SHCIDS_CANONICALONLY, m_pidl, c_desktopPidl) == 0;
      } // End get
    }

    public bool IsEncrypted
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_ENCRYPTED) != 0;
      }
    }

    public bool IsFileSystem
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_FILESYSTEM) != 0;
      }
    }

    public bool IsFolder
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_FOLDER) != 0;
      }
    }

    public bool IsGhosted
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_GHOSTED) != 0;
      }
    }

    public bool IsHidden
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_HIDDEN) != 0;
      }
    }

    public bool IsLink
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_LINK) != 0;
      }
    }

    public bool IsReadOnly
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_READONLY) != 0;
      }
    }

    public bool IsRemovable
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_REMOVABLE) != 0;
      }
    }

    public bool IsShared
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_SHARE) != 0;
      }
    }

    public bool IsSlow
    {
      get
      {
        return (m_attributes & SFGAO.SFGAO_ISSLOW) != 0;
      }
    }

    public bool IsSmallIcon
    {
      get
      {
        return _flags == FileInfo.SHGFI_SMALLICON;
      }
    }

    public string PhysicalPath
    {
      get
      {
        return m_physicalPath;
      }
    }

    public IntPtr PIDL
    {
      get
      {
        return m_pidl;
      }
    }

    public string TypeName
    {
      get
      {
        return m_typeName;
      }
    }


    #endregion


    #region Methods


    public IShellFolder GetShellFolder()
    {
      // Get the IShellFolder for folder
      IntPtr unknownParentFolder = IntPtr.Zero;

      int nResult = c_desktopFolder.BindToObject(PIDL, IntPtr.Zero, ref IID_IShellFolder, out unknownParentFolder);

      if (S_OK != nResult || unknownParentFolder == IntPtr.Zero)
      {
        return null;
      }

      return (IShellFolder)Marshal.GetTypedObjectForIUnknown(unknownParentFolder, typeof(IShellFolder));
    }


    public void Dispose()
    {
      Close();
    }


    public override bool Equals(object obj)
    {
      // Recover a reference to the Pidl object.
      var pidl = obj as Pidl;

      // Sanity check the type first.
      if (pidl == null)
      {
        return false;
      }

      // Ask the desktop to determine if they are equal.
      return c_desktopFolder.CompareIDs(SHCIDS.SHCIDS_CANONICALONLY, m_pidl, pidl.m_pidl) == 0;
    }


    public override int GetHashCode()
    {
      return base.GetHashCode() ^ m_pidl.GetHashCode();
    }


    public void Open(IntPtr pidl, FileInfo flags)
    {
      // Clone to pidl.
      m_pidl = Shell32Methods.ILClone(pidl);

      // Initialize the object.
      InitializeObject(flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public void Open(IntPtr parentPidl, IntPtr pidl, FileInfo flags)
    {
      // Create a fully qualified pidl for the object.
      m_pidl = Shell32Methods.ILCombine(parentPidl, pidl);

      // Initialize the object.
      InitializeObject(flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public void Open(Environment.SpecialFolder specialFolder, FileInfo flags)
    {
      var hToken = new IntPtr(-1);

      // Get the pidl for the special folder.
      int hr = Shell32Methods.SHGetFolderLocation(IntPtr.Zero, SpecialFolderToCSIDL(specialFolder), hToken, 0,
                                                  out m_pidl);

      // Did we fail?
      if (hr != 0)
      {
        Marshal.ThrowExceptionForHR(hr);
      }

      // Initialize the object.
      //FileInfo flags = FileInfo.SHGFI_PIDL | FileInfo.SHGFI_DISPLAYNAME | FileInfo.SHGFI_ATTRIBUTES | FileInfo.SHGFI_TYPENAME;
      InitializeObject(flags);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public void Open(string fullPath, FileInfo flags)
    {
      SFGAO attr = 0;
      uint pchEaten = 0;

      // Attempt to get a pidl to the object.
      int hr = c_desktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, fullPath, ref pchEaten, out m_pidl, ref attr);

      // Did we fail?
      if (hr != 0)
      {
        Marshal.ThrowExceptionForHR(hr);
      }

      // Initialize the object.
      InitializeObject(flags);
    }


    public void Close()
    {
      // Should we cleanup the pidl?
      if (m_pidl != IntPtr.Zero)
      {
        Marshal.FreeCoTaskMem(m_pidl);
      }

      m_pidl = IntPtr.Zero;
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public Pidl GetParentFolder()
    {
      // Sanity check the pidl before attempting to use it.
      if (m_pidl == IntPtr.Zero)
      {
        return null;
      }

      // The desktop folder doesn't have a parent.
      if (c_desktopFolder.CompareIDs(SHCIDS.SHCIDS_ALLFIELDS, m_pidl, c_desktopPidl) == 0)
      {
        return null;
      }

      // Start by copying our pidl since we are about to 
      // modify it to create a pidl for the parent.
      IntPtr parentPidl = Shell32Methods.ILClone(m_pidl);

      // If we failed to remove the last item in the PIDL then
      //   there really isn't any way to recover.
      if (!Shell32Methods.ILRemoveLastID(parentPidl))
      {
        // Free the cloned pidl.
        Marshal.FreeCoTaskMem(parentPidl);

        return null;
      } // End if we failed to locate the parent.

      // Otherwise, create a Pidl and return it.
      return new Pidl(parentPidl,
                      FileInfo.SHGFI_PIDL | FileInfo.SHGFI_DISPLAYNAME | FileInfo.SHGFI_ATTRIBUTES
                      | FileInfo.SHGFI_TYPENAME);
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public List<Pidl> GetAncestors()
    {
      var list = new List<Pidl>();

      // Sanity check the pidl before attempting to use it.
      if (m_pidl == IntPtr.Zero)
      {
        return list;
      }

      var pidl = (Pidl)Clone();

      // Loop and find the ancestors.
      while (pidl != null)
      {
        list.Add(pidl);
        pidl = pidl.GetParentFolder();
      } // End while there are more ancestors.

      // Return the family tree.
      return list;
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    private void InitializeObject(FileInfo flags)
    {
      var shfi = new SHFILEINFO();

      // Attempt to get the information for the shell object.

      Shell32Methods.SHGetFileInfo(m_pidl, 0, ref shfi, Marshal.SizeOf(shfi), flags | FileInfo.SHGFI_PIDL);

      // Save the information.
      m_displayName = shfi.szDisplayName;
      m_typeName = shfi.szTypeName;
      m_attributes = (SFGAO)shfi.dwAttributes;

      var sb = new StringBuilder(260);

      // Get the physical path to the shell object.
      Shell32Methods.SHGetPathFromIDList(m_pidl, sb);

      // Save the path.
      m_physicalPath = sb.ToString();

      if ((flags & FileInfo.SHGFI_ICON) == FileInfo.SHGFI_ICON)
      {
        _icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();
        User32Methods.DestroyIcon(shfi.hIcon);
      }

      _flags = flags;
    }


    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public int GetIconOverlayIndex()
    {
      var shfi = new SHFILEINFO();

      Shell32Methods.SHGetFileInfo(m_pidl, 0, ref shfi, Marshal.SizeOf(shfi),
                                   FileInfo.SHGFI_PIDL | FileInfo.SHGFI_ICON | FileInfo.SHGFI_OVERLAYINDEX);

      return shfi.iIcon >> 24;
    }


    private static CSIDL SpecialFolderToCSIDL(Environment.SpecialFolder sf)
    {
      switch (sf)
      {
        case Environment.SpecialFolder.ApplicationData:

          return CSIDL.CSIDL_APPDATA;

        case Environment.SpecialFolder.CommonApplicationData:

          return CSIDL.CSIDL_COMMON_APPDATA;

        case Environment.SpecialFolder.CommonProgramFiles:

          return CSIDL.CSIDL_COMMON_PROGRAMS;

        case Environment.SpecialFolder.Cookies:

          return CSIDL.CSIDL_COOKIES;

        case Environment.SpecialFolder.Desktop:

          return CSIDL.CSIDL_DESKTOP;

        case Environment.SpecialFolder.DesktopDirectory:

          return CSIDL.CSIDL_DESKTOPDIRECTORY;

        case Environment.SpecialFolder.Favorites:

          return CSIDL.CSIDL_FAVORITES;

        case Environment.SpecialFolder.History:

          return CSIDL.CSIDL_HISTORY;

        case Environment.SpecialFolder.InternetCache:

          return CSIDL.CSIDL_INTERNET_CACHE;

        case Environment.SpecialFolder.LocalApplicationData:

          return CSIDL.CSIDL_LOCAL_APPDATA;

        case Environment.SpecialFolder.MyComputer:

          return CSIDL.CSIDL_DRIVES;

        case Environment.SpecialFolder.MyMusic:

          return CSIDL.CSIDL_MYMUSIC;

        case Environment.SpecialFolder.MyPictures:

          return CSIDL.CSIDL_MYPICTURES;

        case Environment.SpecialFolder.Personal:

          return CSIDL.CSIDL_PERSONAL;

        case Environment.SpecialFolder.ProgramFiles:

          return CSIDL.CSIDL_PROGRAM_FILES;

        case Environment.SpecialFolder.Programs:

          return CSIDL.CSIDL_PROGRAMS;

        case Environment.SpecialFolder.Recent:

          return CSIDL.CSIDL_RECENT;

        case Environment.SpecialFolder.SendTo:

          return CSIDL.CSIDL_SENDTO;

        case Environment.SpecialFolder.StartMenu:

          return CSIDL.CSIDL_STARTMENU;

        case Environment.SpecialFolder.Startup:

          return CSIDL.CSIDL_STARTUP;

        case Environment.SpecialFolder.System:

          return CSIDL.CSIDL_SYSTEM;

        case Environment.SpecialFolder.Templates:

          return CSIDL.CSIDL_TEMPLATES;
      } // End switch

      return CSIDL.CSIDL_DESKTOP;
    }


    public int CompareTo(object obj)
    {
      // Recover a reference to the Pidl object.
      var pidl = obj as Pidl;

      // Sanity check the type first.
      if (pidl == null)
      {
        return 0;
      }

      // Ask the shell to do the compare.
      return c_desktopFolder.CompareIDs(SHCIDS.SHCIDS_CANONICALONLY, pidl.PIDL, m_pidl);
    }


    public object Clone()
    {
      // Clone the object.
      var pidl = new Pidl();
      pidl.m_pidl = Shell32Methods.ILClone(m_pidl);
      pidl.m_displayName = m_displayName;
      pidl.m_typeName = m_typeName;
      pidl.m_attributes = m_attributes;
      pidl.m_physicalPath = m_physicalPath;

      // Return the clone.
      return pidl;
    }


    #endregion
  }
}